/*
 * abuf - Append buffer library
 * author	: calvin
 * email	: calvinwilliams@163.com
 *
 * Licensed under the LGPL v2.1, see the file LICENSE in base directory.
 */

#include "abuf.h"

struct AppendBufferOptions
{
	size_t		init_buf_size ;
	size_t		double_buf_size_on_less_than ;
	size_t		increment_buf_size ;
	size_t		max_buf_size ;
} g_abuf_options = { ABUF_INIT_BUF_SIZE , ABUF_DOUBLE_BUF_SIZE_ON_LESS_THAN , ABUF_INCREMENT_BUF_SIZE , ABUF_MAX_BUF_SIZE } ;

struct AppendBuffer
{
	char				*base ;
	size_t				buf_size ;
	char				*fill_ptr ;
	char				*parse_ptr ;
	struct AppendBufferOptions	abuf_options ;
} ;

struct AppendBuffer *AllocBuffer()
{
	return AllocBufferInitSize( g_abuf_options.init_buf_size );
}

struct AppendBuffer *AllocBufferInitSize( size_t buf_size )
{
	struct AppendBuffer	*buf = NULL ;
	
	if( buf_size > g_abuf_options.max_buf_size )
		return NULL;
	
	buf = (struct AppendBuffer *)malloc( sizeof(struct AppendBuffer) ) ;
	if( buf == NULL )
		return NULL;
	memset( buf , 0x00 , sizeof(struct AppendBuffer) );
	
	buf->buf_size = buf_size ;
	buf->base = (char *)malloc( buf->buf_size ) ;
	if( buf->base == NULL )
	{
		free( buf );
		return NULL;
	}
	memset( buf->base , 0x00 , buf->buf_size );
	
	buf->fill_ptr = buf->base ;
	buf->parse_ptr = buf->base ;
	
	buf->abuf_options.init_buf_size = g_abuf_options.init_buf_size ;
	buf->abuf_options.double_buf_size_on_less_than = g_abuf_options.double_buf_size_on_less_than ;
	buf->abuf_options.increment_buf_size = g_abuf_options.increment_buf_size ;
	buf->abuf_options.max_buf_size = g_abuf_options.max_buf_size ;
	
	return buf;
}

struct AppendBuffer *AllocBufferFromString( char *str )
{
	size_t			str_len ;
	struct AppendBuffer	*buf = NULL ;
	
	str_len = strlen(str) ;
	
	buf = AllocBufferInitSize( str_len+1 ) ;
	if( buf == NULL )
		return NULL;
	memcpy( buf->base , str , str_len );
	
	return buf;
}

struct AppendBuffer *AllocBufferFromMemoryBytes( char *mem , size_t mem_len )
{
	struct AppendBuffer	*buf = NULL ;
	
	buf = AllocBufferInitSize( mem_len+1 ) ;
	if( buf == NULL )
		return NULL;
	memcpy( buf->base , mem , mem_len );
	
	return buf;
}

void FreeBuffer( struct AppendBuffer *buf )
{
	if( buf )
	{
		if( buf->base )
			free( buf->base );
		
		free( buf );
	}
	
	return;
}

void ResetAppendBuffer( struct AppendBuffer *buf )
{
	buf->fill_ptr = buf->base ;
	buf->parse_ptr = buf->base ;
	return;
}

void ResetAppendBufferFillPtr( struct AppendBuffer *buf )
{
	buf->fill_ptr = buf->base ;
	return;
}

ssize_t AppendBufferChar( struct AppendBuffer *buf , char ch )
{
	int		nret = 0 ;
	
	while( buf->fill_ptr-buf->base + 1 >= buf->buf_size )
	{
		nret = IncrementAppendBuffer( buf ) ;
		if( nret )
			return -2;
	}
	
	*(buf->fill_ptr) = ch ;
	
	buf->fill_ptr++;
	*(buf->fill_ptr) = '\0' ;
	
	return 1;
}

/*
#1 : buf_size = 4
     012
        34

#1 : buf_size = 4
     012
        3
*/

ssize_t AppendBufferString( struct AppendBuffer *buf , char *str )
{
	size_t		str_len ;
	
	int		nret = 0 ;
	
	str_len = strlen(str) ;
	if( str_len <= 0 )
		return -1;
	
	while( buf->fill_ptr-buf->base + str_len >= buf->buf_size )
	{
		nret = IncrementAppendBuffer( buf ) ;
		if( nret )
			return -2;
	}
	
	if( str_len == 1 )
		*(buf->fill_ptr) = (*str) ;
	else
		memcpy( buf->fill_ptr , str , str_len );
	
	buf->fill_ptr += str_len ;
	*(buf->fill_ptr) = '\0' ;
	
	return str_len;
}

ssize_t AppendBufferFormatString( struct AppendBuffer *buf , char *format , ... )
{
	va_list		valist ;
	va_list		valist_copy ;
	int		remain_buf_size ;
	int		len ;
	
	int		nret = 0 ;
	
	va_start( valist , format );
	va_copy( valist_copy , valist );
_REPEAT :
	remain_buf_size = buf->buf_size-(buf->fill_ptr-buf->base) ;
	len = vsnprintf( buf->fill_ptr , remain_buf_size , format , valist ) ;
	if( len >= remain_buf_size )
	{
		nret = IncrementAppendBuffer( buf ) ;
		if( nret )
			return -1;
		
		va_copy( valist , valist_copy );
		goto _REPEAT;
	}
	va_end( valist );
	
	buf->fill_ptr += len ;
	*(buf->fill_ptr) = '\0' ;
	
	return len;
}

ssize_t AppendBufferFormatStringV( struct AppendBuffer *buf , char *format , va_list valist )
{
	va_list		valist_copy ;
	int		remain_buf_size ;
	int		len ;
	
	int		nret = 0 ;
	
	va_copy( valist_copy , valist );
_REPEAT :
	remain_buf_size = buf->buf_size-(buf->fill_ptr-buf->base) ;
	len = vsnprintf( buf->fill_ptr , remain_buf_size , format , valist ) ;
	if( len >= remain_buf_size )
	{
		nret = IncrementAppendBuffer( buf ) ;
		if( nret )
			return -1;
		
		va_copy( valist , valist_copy );
		goto _REPEAT;
	}
	
	buf->fill_ptr += len ;
	*(buf->fill_ptr) = '\0' ;
	
	return len;
}

ssize_t AppendBufferMemoryBytes( struct AppendBuffer *buf , char *mem , size_t mem_len )
{
	int		nret = 0 ;
	
	while( buf->fill_ptr-buf->base + mem_len >= buf->buf_size )
	{
		nret = IncrementAppendBuffer( buf ) ;
		if( nret )
			return -1;
	}
	
	memcpy( buf->fill_ptr , mem , mem_len );
	buf->fill_ptr += mem_len ;
	*(buf->fill_ptr) = '\0' ;
	
	return mem_len;
}

ssize_t OffsetAppendBufferFillPtr( struct AppendBuffer *buf , size_t offset )
{
	if( buf->fill_ptr + offset > buf->base + buf->buf_size-1 )
		return -1;
	
	buf->fill_ptr += offset ;
	
	return offset;
}

ssize_t TryOffsetAppendBufferFillPtr( struct AppendBuffer *buf , size_t offset )
{
	int		nret = 0 ;
	
	while( buf->fill_ptr-buf->base + offset >= buf->buf_size )
	{
		nret = IncrementAppendBuffer( buf ) ;
		if( nret < 0 )
			return nret;
	}
	
	return offset;
}

void ResetAppendBufferParsePtr( struct AppendBuffer *buf )
{
	buf->parse_ptr = buf->base ;
	return;
}

unsigned char CheckAppendBufferParseEnd( struct AppendBuffer *buf )
{
	if( buf->parse_ptr == buf->fill_ptr )
		return 1;
	else
		return 0;
}

size_t GetAppendBufferParseInstanceToEnd( struct AppendBuffer *buf )
{
	return buf->fill_ptr-buf->parse_ptr;
}

ssize_t OffsetAppendBufferParsePtr( struct AppendBuffer *buf , size_t offset )
{
	if( buf->parse_ptr + offset > buf->fill_ptr )
		return -1;
	
	buf->parse_ptr += offset ;
	return offset;
}

size_t GetAppendBufferSize( struct AppendBuffer *buf )
{
	return buf->buf_size;
}

char *GetAppendBufferBase( struct AppendBuffer *buf )
{
	return buf->base;
}

char *GetAppendBufferFillPtr( struct AppendBuffer *buf )
{
	return buf->fill_ptr;
}

char *GetAppendBufferParsePtr( struct AppendBuffer *buf )
{
	return buf->parse_ptr;
}

size_t GetAppendBufferLength( struct AppendBuffer *buf )
{
	return buf->fill_ptr-buf->base;
}

size_t *GetAppendBufferSizePtr( struct AppendBuffer *buf )
{
	return & (buf->buf_size);
}

char **GetAppendBufferBasePtr( struct AppendBuffer *buf )
{
	return & (buf->base);
}

char **GetAppendBufferFillPtrPtr( struct AppendBuffer *buf )
{
	return & (buf->fill_ptr);
}

char **GetAppendBufferParsePtrPtr( struct AppendBuffer *buf )
{
	return & (buf->parse_ptr);
}

/*
#1 : buf_size = 4
     0123
       ^
     fill_ptr           new_fill_ptr_offset = 2
     new_buf_size = 2
     01
      ^
     fill_ptr           new_fill_ptr_offset = 1

#2 : buf_size = 4
     0123
      ^
     fill_ptr           new_fill_ptr_offset = 1
     new_buf_size = 2
     01
      ^
     fill_ptr           new_fill_ptr_offset = 1
*/

int ResizeAppendBuffer( struct AppendBuffer *buf , size_t new_buf_size )
{
	char	*new_base = NULL ;
	size_t	new_fill_ptr_offset ;
	size_t	new_parse_ptr_offset ;
	
	if( new_buf_size == buf->buf_size )
		return 1;
	if( new_buf_size >= buf->abuf_options.max_buf_size )
		return 2;
	
	new_fill_ptr_offset = buf->fill_ptr - buf->base ;
	new_parse_ptr_offset = buf->parse_ptr - buf->base ;
	
	new_base = (char *)realloc( buf->base , new_buf_size ) ;
	if( new_base == NULL )
		return -1;
	buf->base = new_base ;
	if( new_fill_ptr_offset >= new_buf_size )
		new_fill_ptr_offset = new_buf_size - 1 ;
	buf->fill_ptr = buf->base + new_fill_ptr_offset ;
	if( new_parse_ptr_offset >= new_buf_size )
		new_parse_ptr_offset = new_buf_size - 1 ;
	buf->parse_ptr = buf->base + new_parse_ptr_offset ;
	if( new_buf_size > buf->buf_size )
		memset( buf->fill_ptr , 0x00 , new_buf_size-buf->buf_size );
	else if( new_buf_size < buf->buf_size )
		*(buf->fill_ptr) = '\0' ;
	buf->buf_size = new_buf_size ;
	
	return 0;
}

int IncrementAppendBuffer( struct AppendBuffer *buf )
{
	size_t	new_buf_size ;
	char	*new_base = NULL ;
	size_t	new_fill_ptr_offset ;
	size_t	new_parse_ptr_offset ;
	
	new_fill_ptr_offset = buf->fill_ptr - buf->base ;
	new_parse_ptr_offset = buf->parse_ptr - buf->base ;
	
	new_buf_size = buf->buf_size ;
	if( new_buf_size <= buf->abuf_options.double_buf_size_on_less_than )
		new_buf_size *= 2 ;
	else
		new_buf_size += buf->abuf_options.increment_buf_size ;
	if( new_buf_size > buf->abuf_options.max_buf_size )
		new_buf_size = buf->abuf_options.max_buf_size ;
	
	if( new_buf_size == buf->buf_size )
		return 1;
	
	new_base = (char *)realloc( buf->base , new_buf_size ) ;
	if( new_base == NULL )
		return -1;
	buf->base = new_base ;
	buf->fill_ptr = buf->base + new_fill_ptr_offset ;
	buf->parse_ptr = buf->base + new_parse_ptr_offset ;
	memset( buf->fill_ptr , 0x00 , new_buf_size-buf->buf_size );
	buf->buf_size = new_buf_size ;
	
	return 0;
}

int TestAndIncrementAppendBuffer( struct AppendBuffer *buf , size_t add_len )
{
	int		nret = 0 ;
	
	while( buf->fill_ptr-buf->base + add_len >= buf->buf_size )
	{
		nret = IncrementAppendBuffer( buf ) ;
		if( nret < 0 )
			return nret;
	}
	
	return 0;
}

void CleanAppendBuffer( struct AppendBuffer *buf )
{
	buf->fill_ptr = buf->base ;
	buf->parse_ptr = buf->base ;
	return;
}

void SetInitBufferSize( struct AppendBuffer *buf , size_t init_buf_size )
{
	buf->abuf_options.init_buf_size = init_buf_size ;
	return;
}

void SetDoubleBufferSizeOnLessThan( struct AppendBuffer *buf , size_t double_buf_size_on_less_than )
{
	buf->abuf_options.double_buf_size_on_less_than = double_buf_size_on_less_than ;
	return;
}

void SetIncrementBufferSize( struct AppendBuffer *buf , size_t increment_buf_size )
{
	buf->abuf_options.increment_buf_size = increment_buf_size ;
	return;
}

void SetMaxBufferSize( struct AppendBuffer *buf , size_t max_buf_size )
{
	buf->abuf_options.max_buf_size = max_buf_size ;
	return;
}

void SetGlobalInitBufferSize( size_t init_buf_size )
{
	g_abuf_options.init_buf_size = init_buf_size ;
	return;
}

void SetGlobalDoubleBufferSizeOnLessThan( size_t double_buf_size_on_less_than )
{
	g_abuf_options.double_buf_size_on_less_than = double_buf_size_on_less_than ;
	return;
}

void SetGlobalIncrementBufferSize( size_t increment_buf_size )
{
	g_abuf_options.increment_buf_size = increment_buf_size ;
	return;
}

void SetGlobalMaxBufferSize( size_t max_buf_size )
{
	g_abuf_options.max_buf_size = max_buf_size ;
	return;
}

